﻿using System;
using System.Reflection;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;

namespace MugenInjection.Parameters
{
    /// <summary>
    /// Represents the base class for parameters.
    /// </summary>
    public class InjectionParameter : IInjectionParameter
    {
        #region Fields

        /// <summary>
        /// Gets the empty parameter name.
        /// </summary>
        public const string EmptyParameterName = "~EmptyParameterName~";

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="InjectionParameter"/> class.
        /// </summary>
        protected InjectionParameter(MemberTypes memberType, string name, ParameterDelegate<object> getValueDelegate)
            : this(getValueDelegate, null, memberType)
        {
            Validate.ArgumentNotNullOrEmpty(name, "name");
            // ReSharper disable DoNotCallOverridableMethodsInConstructor
            Name = name;
            // ReSharper restore DoNotCallOverridableMethodsInConstructor
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="InjectionParameter"/> class.
        /// </summary>
        protected InjectionParameter(MemberTypes memberType, string name, object value)
            : this(value, null, memberType)
        {
            Validate.ArgumentNotNullOrEmpty(name, "name");
            // ReSharper disable DoNotCallOverridableMethodsInConstructor
            Name = name;
            // ReSharper restore DoNotCallOverridableMethodsInConstructor
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="InjectionParameter"/> class.
        /// </summary>
        public InjectionParameter(object value, Func<IInjectionParameter, MemberInfo, ParameterInfo, bool> canResolveDelegate, MemberTypes member)
            : this(context => value, canResolveDelegate, member)
        {
            if (value == null) return;
            // ReSharper disable DoNotCallOverridableMethodsInConstructor
            ParameterType = value.GetType();
            // ReSharper restore DoNotCallOverridableMethodsInConstructor  
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="InjectionParameter"/> class.
        /// </summary>
        public InjectionParameter(ParameterDelegate<object> getValueDelegate, Func<IInjectionParameter, MemberInfo, ParameterInfo, bool> canResolveDelegate, MemberTypes member)
        {
            Validate.ArgumentNotNull(getValueDelegate, "getValueDelegate");
            CanResolveDelegate = canResolveDelegate;
            GetValueDelegate = getValueDelegate;
            // ReSharper disable DoNotCallOverridableMethodsInConstructor
            MemberType = member;
            // ReSharper restore DoNotCallOverridableMethodsInConstructor
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the specified delegate to create parameter value.
        /// </summary>
        public ParameterDelegate<object> GetValueDelegate { get; protected set; }

        /// <summary>
        /// Gets or sets the specified delegate to determines whether the specified request can be resolved.
        /// </summary>
        public Func<IInjectionParameter, MemberInfo, ParameterInfo, bool> CanResolveDelegate { get; set; }

        #endregion

        #region Methods

        protected bool IsParameterForMember(MemberInfo memberInfo)
        {
#if NETFX_CORE
            return memberInfo.GetMemberType() == MemberType;
#else
            return memberInfo.MemberType == MemberType;
#endif
        }

        protected bool IsAssignableParameter(Type parameter)
        {
            return ParameterType == null || parameter.IsAssignableFrom(ParameterType);
        }

        /// <summary>
        /// Determines whether the specified request can be resolved, this method will be invoked if <c>CanResolveDelegate</c> is null.
        /// </summary>
        /// <param name="memberInfo">The specified <see cref="MemberInfo"/></param>
        /// <param name="parameter">The specified <see cref="ParameterInfo"/> </param>
        /// <returns><c>True</c> if the specified request has been resolved; otherwise, <c>false</c>.</returns>
        protected virtual bool CanResolveInternal(MemberInfo memberInfo, ParameterInfo parameter)
        {
            return true;
        }

        #endregion

        #region Implementation of IInjectionParameter

        /// <summary>
        /// Gets the specified parameter name.
        /// </summary>
        public virtual string Name { get; protected set; }

        /// <summary>
        /// Gets the specified parameter <see cref="Type"/>.
        /// </summary>
        public virtual Type ParameterType { get; set; }

        /// <summary>
        /// Gets the specified parameter <see cref="MemberTypes"/>.
        /// </summary>
        public virtual MemberTypes MemberType { get; protected set; }

        /// <summary>
        /// Determines whether the specified request can be resolved.
        /// </summary>
        /// <param name="memberInfo">The specified <see cref="MemberInfo"/></param>
        /// <param name="parameter">The specified <see cref="ParameterInfo"/> </param>
        /// <returns><c>True</c> if the specified request has been resolved; otherwise, <c>false</c>.</returns>
        public virtual bool CanResolve(MemberInfo memberInfo, ParameterInfo parameter)
        {
            if (!IsParameterForMember(memberInfo))
                return false;
            if (CanResolveDelegate != null)
                return CanResolveDelegate(this, memberInfo, parameter);
            return CanResolveInternal(memberInfo, parameter);
        }

        /// <summary>
        /// Gets parameter value for specified <see cref="IParameterContext"/>.
        /// </summary>
        /// <param name="parameterContext">The specified <see cref="IParameterContext"/>.</param>
        /// <returns>An instance of the parameter value.</returns>
        public virtual object GetValue(IParameterContext parameterContext)
        {
            return GetValueDelegate(parameterContext);
        }

        #endregion

        #region Equality members

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.
        ///                 </param>
        public virtual bool Equals(IInjectionParameter other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return string.Equals(Name, other.Name) && MemberType == other.MemberType;
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>. 
        ///                 </param><exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.
        ///                 </exception>
        public sealed override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((InjectionParameter)obj);
        }

        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ (int)MemberType;
            }
        }

        #endregion
    }
}